import grammatica.Empty;
import grammatica.NonTerminal;
import grammatica.Rule;
import grammatica.Grammatica;
import grammatica.Terminal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


class Parser implements TokenKinds {

	/**
	 * De scanner die de tokens levert voor deze parser.
	 */
	private Scanner scanner;

	/**
	 * 1 lookahead voor een LL(1) parser.
	 */
	private Token lookahead;
	private Grammatica grammatica;
	private Rule rule;

	/**
	 * Een fout is opgetreden bij het parsen van de nonterminal.
	 * 
	 * @param nonterminal
	 *            naam van nonterminal
	 * @param message
	 *            foutmelding
	 */
	private void parseError(String nonterminal, String message) {
		System.err.println();
		System.err.println("parse error in <" + nonterminal + "> : " + message);
		System.err.print("lookahead '" + lookahead.getText() + "' ");
		System.err.print("(kind=" + lookahead.getKind() + ") ");
		System.err.print("at " + lookahead.getLine() + "/"
				+ lookahead.getColumn());
		System.err.println();
		throw new RuntimeException();
	}

	/**
	 * Accepteer huidige token van scanner.
	 */
	private void acceptIt() {
		try {
			lookahead = scanner.nextToken();
		} catch (IOException exception) {
			exception.printStackTrace();
			throw new RuntimeException();
		}
	}

	/**
	 * Accepteer huidige token van scanner, mits deze van een bepaald soort is.
	 * 
	 * @param nonterminal
	 *            naam van nonterminal
	 * @param kind
	 *            identifatie van token soort
	 */
	private void accept(String nonterminal, int kind) {
		if (lookahead.getKind() != kind) {
			parseError(nonterminal, "kind=" + kind + " expected");
		} else {
			acceptIt();
		}

	}

	/**
	 * Construeer een nieuwe parser op de input stream.
	 * 
	 * @param in
	 *            input stream van characters
	 */
	public Parser(InputStream in) {
		scanner = new Scanner(new BufferedReader(new InputStreamReader(in)));
		// Initialiseer lookahead met eerste token van scanner
		acceptIt();
	}

	/**
	 * Parse het root symbool van de grammatica.
	 * 
	 * @return het resultaat van parsing (in dit geval een string)
	 */
	public Grammatica parseRoot() {
		grammatica = new Grammatica();

		parseBnf();
		accept("root", EOF);
		if(rule != null)rule.finishDefinition();
		
		//check of die ll1 is
		System.out.println("Parsen gelukt");
		return grammatica;
	}

	private void parseBnf() {
		if(rule != null)rule.finishDefinition();
		rule = grammatica.addDefinition( lookahead.getText() );

		parseRule();

		parseRestRules();
	}

	private void parseRule() {
		accept("NonTerminal", NONTERMINAL);
		accept("isDefinedAs", IS_DEFINED_AS);

		parseAlternatives();
		if (lookahead.getKind() == EOL)
			accept("EOL", EOL);
	}

	private void parseAlternatives() {
		parseOneAlternative();
		parseOtherAlternative();
	}

	private void parseOneAlternative() {
		switch (lookahead.getKind()) {
		case TERMINAL:
			rule.define(new Terminal(lookahead.getText()));
			parseTerm();
			parseRestAlternative();
			break;
		case NONTERMINAL:
			rule.define(new NonTerminal(lookahead.getText()));
			parseTerm();
			parseRestAlternative();
			break;
		case EMPTY:			
			rule.finishDefinition();
			rule.define(new Empty());
			accept("Empty", EMPTY);
			break;
		default:
			;
		}
	}

	private void parseOtherAlternative() {
		switch (lookahead.getKind()) {
		case OR:
			rule.finishDefinition();
			accept("|", OR);
			parseAlternatives();
			break;
		case EMPTY:
			rule.finishDefinition();
			rule.define(new Empty());
			accept("Empty", EMPTY);
		default:
			;
		}
		
	}

	private void parseRestRules() {
		switch (lookahead.getKind()) {
		case NONTERMINAL:
			parseBnf();
			break;
		case EMPTY:

			accept("Empty", EMPTY);
		default:
			;
		}
	}

	private void parseRestAlternative() {
		switch (lookahead.getKind()) {
		case TERMINAL:
			rule.define(new Terminal(lookahead.getText()));
			parseTerm();
			parseRestAlternative();
			break;
		case NONTERMINAL:
			rule.define(new NonTerminal(lookahead.getText()));
			parseTerm();
			parseRestAlternative();
			break;
		case EMPTY:
			rule.finishDefinition();
			
			accept("Empty", EMPTY);
			break;
		default:
			;
		}

	}

	private void parseTerm() {
		switch (lookahead.getKind()) {
		case TERMINAL:
			accept("Terminal", TERMINAL);
			break;
		case NONTERMINAL:
			accept("NonTerminal", NONTERMINAL);
			break;
		default:
			;
		}

	}
}
